﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CrpytDemo
{
    class MyDESUtil
    {
        public byte[][] subKey = new byte[16][];

        void FillChar(ref byte[] data, int len, byte v)
        {
            for (int i = 0; i < len; i++)
            {
                data[i] = v;
            }
        }

        public void initPermutation(byte[] inData)
        {
            byte[] newData = new byte[8];
            FillChar(ref newData, 8, 0);
            for (int i = 0; i < 64; i++)
            {

                if (((inData[MyDESUtilRes.BitIP[i] >> 3]) & (1 << (7 - (MyDESUtilRes.BitIP[i] & 0x07)))) != 0)
                {
                    newData[i >> 3] = (byte)((newData[i >> 3]) | (1 << (7 - (i & 0x07))));
                }
            }
            for (int i = 0; i < 8; i++) { inData[i] = newData[i]; }
        }
        public void conversePermutation(byte[] inData)
        {
            byte[] newData = new byte[8];
            FillChar(ref newData, 8, 0);
            for (int i = 0; i < 64; i++)
            {
                if (((inData[MyDESUtilRes.BitCP[i] >> 3]) & (1 << (7 - (MyDESUtilRes.BitCP[i] & 0x07)))) != 0)
                {
                    newData[i >> 3] = (byte)((newData[i >> 3]) | (1 << (7 - (i & 0x07))));
                }
            }
            for (int i = 0; i < 8; i++) { inData[i] = newData[i]; }
        }
        public void expand(byte[] inData, out byte[] outData)
        {
            outData = new byte[6];
            FillChar(ref outData, 6, 0);
            for (int i = 0; i < 48; i++)
            {

                if (((inData[MyDESUtilRes.BitExp[i] >> 3]) & (1 << (7 - (MyDESUtilRes.BitExp[i] & 0x07)))) != 0)
                    outData[i >> 3] = (byte)((outData[i >> 3]) | (1 << (7 - (i & 0x07))));
            }
        }
        public void permutation(byte[] inData)
        {

            byte[] newData = new byte[4];
            FillChar(ref newData, 4, 0);
            for (int i = 0; i < 32; i++)
            {
                if (((inData[MyDESUtilRes.BitPM[i] >> 3]) & (1 << (7 - (MyDESUtilRes.BitPM[i] & 0x07)))) != 0)
                {
                    newData[i >> 3] = (byte)((newData[i >> 3]) | (1 << (7 - (i & 0x07))));
                }
            }
            for (int i = 0; i < 4; i++) { inData[i] = newData[i]; }
        }
        public byte si(int s, byte inByte)
        {
            byte c;

            c = (byte)((inByte & 0x20) | ((inByte & 0x1E) >> 1) |
            ((inByte & 0x01) << 4));
            return (byte)(MyDESUtilRes.sBox[s, c] & 0x0F);

        }
        public void permutationChoose1(byte[] inData, out byte[] outData)
        {

            outData = new byte[7];
            FillChar(ref outData, 7, 0);
            for (int i = 0; i < 56; i++)
            {
                if (((inData[MyDESUtilRes.BitPMC1[i] >> 3]) & (1 << (7 - (MyDESUtilRes.BitPMC1[i] & 0x07)))) != 0)
                {
                    outData[i >> 3] = (byte)((outData[i >> 3]) | (1 << (7 - (i & 0x07))));
                }
            }
        }
        public void permutationChoose2(byte[] inData, ref byte[] outData)
        {

            outData = new byte[6];
            FillChar(ref outData, 6, 0);
            for (int i = 0; i < 48; i++)
            {
                if (((inData[MyDESUtilRes.BitPMC2[i] >> 3]) & (1 << (7 - (MyDESUtilRes.BitPMC2[i] & 0x07)))) != 0)
                {
                    outData[i >> 3] = (byte)((outData[i >> 3]) | (1 << (7 - (i & 0x07))));
                }
            }
        }
        public void cycleMove(byte[] inData, byte bitMove)
        {
            for (int i = 0; i < bitMove; i++)
            {
                inData[0] = (byte)((inData[0] << 1) | (inData[1] >> 7));
                inData[1] = (byte)((inData[1] << 1) | (inData[2] >> 7));
                inData[2] = (byte)((inData[2] << 1) | (inData[3] >> 7));
                inData[3] = (byte)((inData[3] << 1) | ((inData[0] & 0x10) >> 4));
                inData[0] = (byte)((inData[0] & 0x0F));
            }
        }
        public static readonly byte[] bitDisplace = new byte[16] { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
        public void makeKey(byte[] inKey, byte[][] outKey)
        {
            byte[] outData56 = new byte[7];
            byte[] key28l = new byte[4];
            byte[] key28r = new byte[4];
            byte[] key56o = new byte[7];
            int i = 0;
            permutationChoose1(inKey, out outData56);

            key28l[0] = (byte)(outData56[0] >> 4);
            key28l[1] = (byte)((outData56[0] << 4) | (outData56[1] >> 4));
            key28l[2] = (byte)((outData56[1] << 4) | (outData56[2] >> 4));
            key28l[3] = (byte)((outData56[2] << 4) | (outData56[3] >> 4));
            key28r[0] = (byte)(outData56[3] & 0x0F);
            key28r[1] = outData56[4];
            key28r[2] = outData56[5];
            key28r[3] = outData56[6];
            for (i = 0; i < 16; i++)
            {
                cycleMove(key28l, bitDisplace[i]);
                cycleMove(key28r, bitDisplace[i]);
                key56o[0] = (byte)((key28l[0] << 4) | (key28l[1] >> 4));
                key56o[1] = (byte)((key28l[1] << 4) | (key28l[2] >> 4));
                key56o[2] = (byte)((key28l[2] << 4) | (key28l[3] >> 4));
                key56o[3] = (byte)((key28l[3] << 4) | (key28r[0]));
                key56o[4] = key28r[1];
                key56o[5] = key28r[2];
                key56o[6] = key28r[3];
                permutationChoose2(key56o, ref outKey[i]);
            }
        }
        public void encry(byte[] inData, byte[] subKey, byte[] outData)
        {
            byte[] outBuf = new byte[6];
            byte[] buf = new byte[8];
            expand(inData, out outBuf);
            for (int i = 0; i < 6; i++)
            {
                outBuf[i] = (byte)(outBuf[i] ^ subKey[i]);
            }
            // outBuf       xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
            buf[0] = (byte)(outBuf[0] >> 2); //xxxxxx -> 2
            buf[1] = (byte)(((outBuf[0] & 0x03) << 4) | (outBuf[1] >> 4)); // 4 <- xx xxxx -> 4
            buf[2] = (byte)(((outBuf[1] & 0x0F) << 2) | (outBuf[2] >> 6)); //        2 <- xxxx xx -> 6
            buf[3] = (byte)(outBuf[2] & 0x3F); //                    xxxxxx
            buf[4] = (byte)(outBuf[3] >> 2); //                           xxxxxx
            buf[5] = (byte)(((outBuf[3] & 0x03) << 4) | (outBuf[4] >> 4)); //                                 xx xxxx
            buf[6] = (byte)(((outBuf[4] & 0x0F) << 2) | (outBuf[5] >> 6)); //                                        xxxx xx
            buf[7] = (byte)(outBuf[5] & 0x3F); //                                               xxxxxx
            for (int i = 0; i < 8; i++)
            {
                buf[i] = si(i, buf[i]);
            }
            for (int i = 0; i < 4; i++) outBuf[i] = (byte)((buf[i * 2] << 4) | buf[i * 2 + 1]);
            permutation(outBuf);
            for (int i = 0; i < 4; i++) outData[i] = outBuf[i];
        }
        /// <summary>
        /// cbc模式
        /// </summary>
        /// <param name="inData"></param>
        /// <param name="subKey"></param>
        /// <param name="iv"></param>
        /// <param name="outData"></param>
        public void encryCbc(byte[] inData, byte[] subKey, byte[] iv, byte[] outData)
        {
            byte[] outBuf = new byte[6];
            byte[] buf = new byte[8];
            byte[] tempBuf = new byte[inData.Length];
            for (int i = 0; i < inData.Length; i++)
            {
                int j = i % 8;
                tempBuf[i] = (byte)(inData[i] ^ iv[j]);
            }

            expand(tempBuf, out outBuf);
            for (int i = 0; i < 6; i++)
            {
                outBuf[i] = (byte)(outBuf[i] ^ subKey[i]);
            }
            // outBuf       xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
            buf[0] = (byte)(outBuf[0] >> 2); //xxxxxx -> 2
            buf[1] = (byte)(((outBuf[0] & 0x03) << 4) | (outBuf[1] >> 4)); // 4 <- xx xxxx -> 4
            buf[2] = (byte)(((outBuf[1] & 0x0F) << 2) | (outBuf[2] >> 6)); //        2 <- xxxx xx -> 6
            buf[3] = (byte)(outBuf[2] & 0x3F); //                    xxxxxx
            buf[4] = (byte)(outBuf[3] >> 2); //                           xxxxxx
            buf[5] = (byte)(((outBuf[3] & 0x03) << 4) | (outBuf[4] >> 4)); //                                 xx xxxx
            buf[6] = (byte)(((outBuf[4] & 0x0F) << 2) | (outBuf[5] >> 6)); //                                        xxxx xx
            buf[7] = (byte)(outBuf[5] & 0x3F); //                                               xxxxxx
            for (int i = 0; i < 8; i++)
            {
                buf[i] = si(i, buf[i]);
            }
            for (int i = 0; i < 4; i++) outBuf[i] = (byte)((buf[i * 2] << 4) | buf[i * 2 + 1]);
            permutation(outBuf);
            for (int i = 0; i < 4; i++) outData[i] = outBuf[i];
        }
        public void desData(int desMode, byte[] inData, byte[] outData)
        {
            // inData, outData ??8Bytes,????
            int i, j;
            byte[] temp = new byte[4], buf = new byte[4];

            for (i = 0; i < 8; i++) outData[i] = inData[i];
            initPermutation(outData);
            if (desMode == dmEncry)
            {//加密

                for (i = 0; i < 16; i++)
                {
                    for (j = 0; j < 4; j++) temp[j] = outData[j]; //temp = Ln
                    for (j = 0; j < 4; j++) outData[j] = outData[j + 4]; //Ln+1 = Rn
                    encry(outData, subKey[i], buf); //Rn ==Kn==> buf
                    for (j = 0; j < 4; j++) outData[j + 4] = (byte)(temp[j] ^ buf[j]); //Rn+1 = Ln^buf
                }

                for (j = 0; j < 4; j++) temp[j] = outData[j + 4];
                for (j = 0; j < 4; j++) outData[j + 4] = outData[j];
                for (j = 0; j < 4; j++) outData[j] = temp[j];
            }
            else if (desMode == dmDecry)
            {//解密
                for (i = 15; i >= 0; i--)
                {
                    for (j = 0; j < 4; j++) temp[j] = outData[j];
                    for (j = 0; j < 4; j++) outData[j] = outData[j + 4];
                    encry(outData, subKey[i], buf);
                    for (j = 0; j < 4; j++) outData[j + 4] = (byte)(temp[j] ^ buf[j]);
                }
                for (j = 0; j < 4; j++) temp[j] = outData[j + 4];
                for (j = 0; j < 4; j++) outData[j + 4] = outData[j];
                for (j = 0; j < 4; j++) outData[j] = temp[j];
            }
            conversePermutation(outData);

        }
        public void desCBCData(int desMode, byte[] iv, byte[] inData, byte[] outData)
        {
            // inData, outData ??8Bytes,????
            int i, j;
            byte[] temp = new byte[4], buf = new byte[4];

            for (i = 0; i < 8; i++) outData[i] = inData[i];
            initPermutation(outData);
            if (desMode == dmEncry)
            {//加密

                for (i = 0; i < 16; i++)
                {
                    for (j = 0; j < 4; j++) temp[j] = outData[j]; //temp = Ln
                    for (j = 0; j < 4; j++) outData[j] = outData[j + 4]; //Ln+1 = Rn
                    encryCbc(outData, subKey[i], iv, buf); //Rn ==Kn==> buf
                    for (j = 0; j < 4; j++) outData[j + 4] = (byte)(temp[j] ^ buf[j]); //Rn+1 = Ln^buf
                }

                for (j = 0; j < 4; j++) temp[j] = outData[j + 4];
                for (j = 0; j < 4; j++) outData[j + 4] = outData[j];
                for (j = 0; j < 4; j++) outData[j] = temp[j];
            }
            else if (desMode == dmDecry)
            {//解密
                for (i = 15; i >= 0; i--)
                {
                    for (j = 0; j < 4; j++) temp[j] = outData[j];
                    for (j = 0; j < 4; j++) outData[j] = outData[j + 4];
                    encryCbc(outData, subKey[i], iv, buf);
                    for (j = 0; j < 4; j++) outData[j + 4] = (byte)(temp[j] ^ buf[j]);
                }
                for (j = 0; j < 4; j++) temp[j] = outData[j + 4];
                for (j = 0; j < 4; j++) outData[j + 4] = outData[j];
                for (j = 0; j < 4; j++) outData[j] = temp[j];
            }
            conversePermutation(outData);

        }
        const int dmEncry = 1;
        const int dmDecry = 2;

        private void Padding(ref string key)
        {

            if (key.Length < 8)
            {
                char padding = (char)(8 - key.Length);
                while (key.Length < 8) key = key + padding;// '\0';
            }
            else
            {
                int len = key.Length;
                int paddingSize = 8 - len % 8;
                char padding = (char)((byte)paddingSize);
                for (int i = 0; i < padding; i++)
                {
                    key += padding;
                }
            }
        }

        private void Padding(ref List<byte> key)
        {

            if (key.Count < 8)
            {
                byte padding = (byte)(8 - key.Count);
                while (key.Count < 8) key.Add(padding);// '\0';
            }
            else
            {
                int len = key.Count;
                int paddingSize = 8 - len % 8;
                byte padding = (byte)paddingSize;
                for (int i = 0; i < padding; i++)
                {
                    key.Add(padding);
                }
            }
        }
        public byte[] EncryStr(byte[] Str, string Key)
        {
            byte[] StrByte = new byte[8], OutByte = new byte[8], KeyByte = new byte[8];

            int I, J;
            List<byte> inData = new List<byte>(Str);
            if ((Str.Length > 0))
                new Exception("Error: the last char is NULL char.");
            Padding(ref Key);
            //if (Key.Length < 8)
            //    while (Key.Length < 8) Key = Key + '\0';
            //while (inData.Count % 8 != 0) inData.Add(0x00);

            Padding(ref inData);

            for (J = 0; J < 8; J++) KeyByte[J] = (byte)(Key[J]);

            makeKey(KeyByte, subKey);

            List<byte> result = new List<byte>();

            for (I = 0; I < inData.Count / 8; I++)
            {
                for (J = 0; J < 8; J++)
                    StrByte[J] = (byte)(inData[I * 8 + J]);

                desData(dmEncry, StrByte, OutByte);
                for (J = 0; J < 8; J++)
                    result.Add(OutByte[J]);
            }

            return result.ToArray();
        }
        public byte[] DecryStr(byte[] Str, string Key)
        {
            byte[]
              StrByte = new byte[8], OutByte = new byte[8], KeyByte = new byte[8];
            string StrResult = "";
            int I, J;

            //if (Key.Length < 8) {
            //    char padding = (char)(8-Key.Length);
            //    while (Key.Length < 8) Key = Key + padding;// '\0';
            //}
            Padding(ref Key);

            for (J = 0; J < 8; J++) KeyByte[J] = (byte)(Key[J]);

            makeKey(KeyByte, subKey);

            List<byte> result = new List<byte>();

            for (I = 0; I < Str.Length / 8; I++)
            {
                for (J = 0; J < 8; J++) StrByte[J] = (byte)(Str[I * 8 + J]);
                desData(dmDecry, StrByte, OutByte);
                for (J = 0; J < 8; J++)
                {
                    if (OutByte[J] < 0x08)
                        break;
                    result.Add(OutByte[J]);
                }

            }

            return result.ToArray();
        }

        public byte[] EncryCBCStr(byte[] Str, string Key, string IV)
        {
            byte[] StrByte = new byte[8], OutByte = new byte[8], KeyByte = new byte[8];

            byte[] ivByte = new byte[8];
            int I, J;
            List<byte> inData = new List<byte>(Str);

            Padding(ref Key);
            Padding(ref IV);

            for (J = 0; J < 8; J++)
            {
                KeyByte[J] = (byte)(Key[J]);
                ivByte[J] = (byte)(IV[J]);
            }

            Padding(ref inData);

            for (J = 0; J < 8; J++) KeyByte[J] = (byte)(Key[J]);

            makeKey(KeyByte, subKey);

            List<byte> result = new List<byte>();

            for (I = 0; I < inData.Count / 8; I++)
            {
                for (J = 0; J < 8; J++)
                    StrByte[J] = (byte)(inData[I * 8 + J] ^ ivByte[J]);

                desData(dmEncry, StrByte, OutByte);
                ivByte = OutByte;
                for (J = 0; J < 8; J++)
                    result.Add(OutByte[J]);
            }

            return result.ToArray();
        }
        public byte[] DecryCBCStr(byte[] Str, string Key, string IV)
        {
            byte[] StrByte = new byte[8], OutByte = new byte[8], KeyByte = new byte[8];
            byte[] ivByte = new byte[8];

            int I, J;

            //if (Key.Length < 8) {
            //    char padding = (char)(8-Key.Length);
            //    while (Key.Length < 8) Key = Key + padding;// '\0';
            //}
            Padding(ref Key);
            Padding(ref IV);

            for (J = 0; J < 8; J++)
            {
                KeyByte[J] = (byte)(Key[J]);
                ivByte[J] = (byte)(IV[J]);
            }

            makeKey(KeyByte, subKey);


            List<byte> result = new List<byte>();

            for (I = 0; I < Str.Length / 8; I++)
            {
                //for (J = 0; J < 8; J++) StrByte[J] = (byte)(Str[I * 8 + J]);
                for (J = 0; J < 8; J++)
                    StrByte[J] = (byte)(Str[I * 8 + J]);

                desData(dmDecry, StrByte, OutByte);
                for (J = 0; J < 8; J++)
                {
                    result.Add((byte)(OutByte[J] ^ ivByte[J]));
                    ivByte[J] = StrByte[J];
                }
            }
            int dataLen = result.Count;
            int dstLen = dataLen;
            byte last = result[result.Count - 1];//可能是补位长度
            if (last < 0x08)
            {
                for (int i = 0; i < last; i++)
                {
                    if (result[dataLen - i - 1] != last)
                        return result.ToArray();
                }
                dstLen = dataLen - last;

                for (int i = 0; i < last; i++)
                {
                    result.RemoveAt(dataLen - i - 1);
                }
            }

            return result.ToArray();
        }
    }
}
